#include "dev.h"
#include "irq.h"
#include "tty.h"
#include "klib.h"
#include "console.h"
#include "log.h"

#define DEV_TABLE_SIZE 128
// alloc for specified device, the index is dev_id
static device_t dev_tbl[DEV_TABLE_SIZE];

extern dev_desc_t  dev_tty_desc;
extern dev_desc_t  dev_disk_desc;
static dev_desc_t *dev_desc_tbl[] = {
    &dev_tty_desc,
    &dev_disk_desc,
};

// dev_id belong to the array id in dev_tbl
int dev_open(int major, int minor, void *data)
{
    irq_state_t irq_state = irq_enter_protect();
    device_t   *free_dev  = (device_t *)0;
    for (int i = 0; i < sizeof(dev_tbl) / sizeof(dev_tbl[0]); i++)
    {
        device_t *dev = dev_tbl + i;
        if (!free_dev && dev->open_count == 0)
        {
            free_dev = dev;
        }
        else if ((dev->desc->major == major) && (dev->minor == minor))
        {
            dev->open_count++;
            irq_exit_protect(irq_state);
            return i;
        }
    }
    // find wether the major is in dev_desc_tbl
    dev_desc_t *desc = (dev_desc_t *)0;
    for (int i = 0; i < sizeof(dev_desc_tbl) / sizeof(dev_desc_tbl[0]); i++)
    {
        if (dev_desc_tbl[i]->major == major)
        {
            desc = dev_desc_tbl[i];
            break;
        }
    }
    // the dev_desc was registered and there is free device space exist
    if (desc && free_dev)
    {
        free_dev->desc  = desc;
        free_dev->minor = minor;
        free_dev->data  = data;
        int err         = desc->open(free_dev);
        // success
        if (!err)
        {
            free_dev->open_count = 1;
            irq_exit_protect(irq_state);
            return free_dev - dev_tbl;
        }
    }
    irq_exit_protect(irq_state);
    return -1;
}

static int is_devid_bad(int dev_id)
{
    if (dev_id < 0 || dev_id >= DEV_TABLE_SIZE)
    {
        return 1;
    }
    if ((dev_tbl + dev_id) == (device_t *)0 || dev_tbl[dev_id].desc == (dev_desc_t *)0)
    {
        log_printf("is_devid_bad: dev_id not valid");
        return 1;
    }
    return 0;
}

int dev_read(int dev_id, int addr, char *buf, int size)
{
    if (is_devid_bad(dev_id))
    {
        return -1;
    }
    device_t *dev = dev_tbl + dev_id;
    return dev->desc->read(dev, addr, buf, size);
}
int dev_write(int dev_id, int addr, char *buf, int size)
{
    if (is_devid_bad(dev_id))
    {
        return -1;
    }
    device_t *dev = dev_tbl + dev_id;
    return dev->desc->write(dev, addr, buf, size);
}
int dev_control(int dev_id, int cmd, int arg0, int arg1)
{
    if (is_devid_bad(dev_id))
    {
        return -1;
    }
    device_t *dev = dev_tbl + dev_id;
    return dev->desc->control(dev, cmd, arg0, arg1);
}
void dev_close(int dev_id)
{
    if (is_devid_bad(dev_id))
    {
        return;
    }
    device_t   *dev       = dev_tbl + dev_id;
    irq_state_t irq_state = irq_enter_protect();
    if (--dev->open_count == 0)
    {
        dev->desc->close(dev);
        kernel_memset(dev, 0, sizeof(device_t));
    }
    irq_exit_protect(irq_state);
}